﻿using Microsoft.Xna.Framework;
using System;
using System.Runtime.Serialization;

namespace Innovation
{
    // Basic Camera class
    public class Camera : Component, I3DComponent
    {
        // Internal values
        Vector3 target = new Vector3(0, 0, -1);
        Vector3 up = Vector3.Up;
        Matrix view;
        Matrix projection;

        // The point the camera is looking at
        public virtual Vector3 Target
        { get { return target; } set { target = value; } }

        // The View and Projection matrices commonly used for rendering
        public virtual Matrix View
        { get { return view; } set { view = value; } }
        public virtual Matrix Projection
        { get { return projection; } set { projection = value; } }
        
        public virtual Vector3 Up
        { get { return up; } set { up = value; } }

        // Public I3DComponent values
        Transform transform = new Transform();
        public Transform Transform { get { return transform; } }

        public virtual BoundingBox BoundingBox
        { get { return new BoundingBox(Transform.Position - Vector3.One, Transform.Position + Vector3.One); } }

        // Constructors
        public Camera(GameScreen Parent) : base(Parent) { }
        public Camera() : base() { }

        // Update the camera
        public override void Update()
        {
            // Calculate the direction from the position to the target, and normalize
            Vector3 newForward = Target - Transform.Position;
            newForward.Normalize();

            // Set the rotation matrix's forward to this vector
            Matrix rotationMatrixCopy = this.Transform.Rotation;
            rotationMatrixCopy.Forward = newForward;

            // Save a copy of "Up" (0, 1, 0)
            Vector3 referenceVector = Vector3.Up;

            // On the slim chance that the camera is pointed perfectly parallel with
            // the Y Axis, we cannot use cross product with a parallel axis, so we
            // change the reference vector to the forward axis (Z).
            if (rotationMatrixCopy.Forward.Y == referenceVector.Y
                || rotationMatrixCopy.Forward.Y == -referenceVector.Y)
                referenceVector = Vector3.Backward;

            // Calculate the other parts of the rotation matrix
            rotationMatrixCopy.Right = Vector3.Cross(this.Transform.Rotation.Forward,
                referenceVector);
            rotationMatrixCopy.Up = Vector3.Cross(this.Transform.Rotation.Right,
                this.Transform.Rotation.Forward);

            this.Transform.Rotation = rotationMatrixCopy;

            // Use the rotation matrix to find the new up
            Up = Transform.Rotation.Up;

            // Recalculate View and Projection using the new Position, Target, and Up
            View = Matrix.CreateLookAt(Transform.Position, Target, Up);
            Projection = MathUtil.CreateProjectionMatrix();
        }

        public override void SaveSerializationData(SerializationData Data)
        {
            base.SaveSerializationData(Data);

            Data.AddData("Camera.Position", Transform.Position);
            Data.AddData("Camera.Target", Target);
        }

        public override void LoadFromSerializationData(SerializationData Data)
        {
            base.LoadFromSerializationData(Data);

            this.Transform.Position = Data.GetData<Vector3>("Camera.Position");
            this.Target = Data.GetData<Vector3>("Camera.Target");
        }
    }
}
